use byteorder::{ByteOrder, BigEndian};
use rust_decimal::Decimal;
use tera::{Tera, Context};
use std::ops::Neg;
use num::traits::{FromPrimitive};
use std::str::FromStr;

use crate::parser::{TokenStream, Parser, Token};
use crate::{Result, Error};
use crate::cobol::{self, Spec, DecimalFormat, RecordLike};

#[derive(Debug, Clone)]
pub struct Field {
    level: u32,
    name: String,
    offset: u32,
    spec: Spec,
}
 
impl RecordLike for Field {
    fn name(&self) -> &str {
        &self.name
    }
    fn level(&self) -> u32 {
        self.level
    }
    fn length(&self) -> u32 {
        self.spec.length()
    }
    fn offset(&self) -> u32 {
        self.offset
    }
}
 
#[allow(dead_code)]
impl Field {
    pub fn new(level: u32, name: &str, offset: u32, spec: Spec) -> Field {
        Field{ name: name.to_owned(), level, offset, spec }
    }

    pub fn spec(&self) -> &Spec {
        &self.spec
    }

    pub fn get_decimal(&self, bytes: &[u8]) -> Result<Decimal>{
        let slice = self.get_u8_slice(bytes)?;
        if let Spec::Decimal(d) = &self.spec {
            match d.format {
                DecimalFormat::None => {
                    let final_bytes = if d.sign.is_some() {
                        let sign = d.sign.clone().unwrap();
                        let sigbyte = if sign.leading {
                            bytes[0]
                        } else {
                            *bytes.last().unwrap()
                        };
                        let signval = sigbyte >> 4;
                        let number = sigbyte & 0x0F;
                        if sign.leading {
                            [&[signval], &[number], &bytes[1..]].concat()
                        } else {
                            [&[signval], &bytes[1..], &[number]].concat()
                        }
                    } else {
                        slice.to_vec()
                    };
                    let val = std::str::from_utf8(&final_bytes)?;
                    Ok(Decimal::from_str(val)?)
                },
                DecimalFormat::Packed => {
                    let mut out_bytes: [u8; 8] = [0; 8];
                    let mut is_signed = false;
                    let mut is_negative = false;
                    packed_to_bytes(slice, &mut out_bytes, &mut is_signed, &mut is_negative);
                    let val = std::str::from_utf8(&out_bytes)?;
                    let result = Decimal::from_str(val)?;
                    if is_negative {
                        Ok(result.neg())
                    } else {
                        Ok(result)
                    }
                }, 
                DecimalFormat::UnsignedFullWord => {
                    Ok(Decimal::from_u64(BigEndian::read_u64(bytes)).unwrap())
                }
            }
        } else {
            Err(Error::WrongFieldType{
                name: self.name.clone(),
                expected: "decimal".to_owned(),
                actual: "string".to_owned(),
            })
        } 
    }
    
    pub fn get_string(&self, bytes: &[u8]) -> Result<String> {
        let slice = self.get_u8_slice(bytes)?;
        String::from_utf8(slice.to_vec()).map_err(Error::from)
    }

    fn get_u8_slice<'a>(&self, bytes: &'a [u8]) -> Result<&'a [u8]> {
        let from: usize = self.offset as usize;
        let to: usize = (self.offset + self.spec.length()) as usize;
        let slice = &bytes[from..to];
        Ok(slice)
    }

    pub fn render_init(&self, tera: &Tera) -> String {
        let mut context = Context::new();
        context.insert("level", &self.level);
        context.insert("name", &self.name.replace("-", "_").to_lowercase());
        context.insert("orig_name", &self.name);
        context.insert("offset", &self.offset);
        match &self.spec {
            Spec::Decimal(d) => {
                context.insert("scale", &format!("{:?}", d.scale));
                context.insert("precision", &d.precision);
                context.insert("signed", &d.sign.is_some());
                if let Some(sign) = d.sign.clone() {
                    context.insert("sign_leading", &sign.leading);
                    context.insert("sign_separate", &sign.separate);
                }
                context.insert("format", &format!("{:?}", d.format));
                tera.render("init_field_decimal.tera", &context).unwrap()
            },
            Spec::String(s) => {
                context.insert("length", &s.length);
                tera.render("init_field_string.tera", &context).unwrap()
            }
        }
    }

    pub fn render_getter(&self, tera: &Tera) -> String {
        let mut context = Context::new();
        let name = &self.name.replace("-", "_").to_lowercase();
        context.insert("getter_name", &name);
        context.insert("name", name);
        let getter_type = match &self.spec {
            Spec::String(_) => "String".to_owned(),
            Spec::Decimal(_) => {
                // let len = match d.length() {
                    // 1     => "8",
                    // 1...2 => "16",
                    // 3...4 => "32",
                    // _     => "64",
                // };
                // if d.sign.is_some() {
                    // "i".to_owned() + len
                // } else {
                    // "u".to_owned() + len
                // }
                "Decimal".to_owned()
            }
        };
        context.insert("getter_type_lowercase", &getter_type.to_lowercase());
        context.insert("getter_type", &getter_type);
        tera.render("getter.tera", &context).unwrap()
    }
}

fn packed_to_bytes(bytes: &[u8], out: &mut [u8], is_signed: &mut bool, is_negative: &mut bool){
    let drop_first: u8 = 0x0F;
    let len = bytes.len();
    for (i,b) in bytes.into_iter().enumerate() {
        out[i*2+1] =   b >> 4;
        if i == len - 1 {
            match b & drop_first {
                0x0C | 0x0A | 0x0E => { *is_signed = true; *is_negative = false; },
                0x0D | 0x0B        => { *is_signed = true; *is_negative = true;  },
                0x0F               => { *is_signed = false; },
                _                  => {},
            }
            break;
        }
        out[i*2+2] = b & drop_first;
    }
}

impl Parser for Field {
    fn parse(stream: &mut TokenStream, offset: u32) -> Result<Self> {
        let level = stream.parse_unsigned()?;
        let name = if stream.may_be(Token::Filler) {
            cobol::normalize_name("FILLER", stream)
        } else {
            cobol::normalize_name(&stream.parse_ident()?, stream)
        };
        let offset = if stream.may_be(Token::Redefines){
            let redefined_field = cobol::normalize_name(&stream.parse_ident()?, stream);
            if let Some(redefined_offset) = stream.get_redefine(redefined_field) {
                redefined_offset
            } else {
                offset
            }
        } else {
            offset
        };
        stream.must_be(Token::Pic)?;
        let spec = Spec::try_parse(stream, offset)?;
        stream.must_be(Token::Dot)?;
        stream.save_redefine(name.clone(), offset);
        Ok(Field {
            level, name, offset, spec 
        })
    }
}
